﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpPcap;
using SharpPcap.Packets;
using SharpPcap.Util;
using System.Text.RegularExpressions;

namespace PacketHandler
{
    public class W3GSPacket : BlizzardPacket
    {
        protected int portIdx = -1;
        protected int ipIdx = -1;

        public enum W3GSPacketIDConstants : byte
        {

            // W3GS Message Constants
            // -------------------

            W3GS_PING_FROM_HOST = 0x01,
            W3GS_SLOTINFOJOIN = 0x04,
            W3GS_REJECTJOIN = 0x05,
            W3GS_PLAYERINFO = 0x06,
            W3GS_PLAYERLEAVE_OTHERS = 0x07,
            W3GS_GAMELOADED_OTHERS = 0x08,
            W3GS_SLOTINFO = 0x09,
            W3GS_COUNTDOWN_START = 0x0A,
            W3GS_COUNTDOWN_END = 0x0B,
            W3GS_INCOMING_ACTION = 0x0C,
            W3GS_CHAT_FROM_HOST = 0x0F,
            W3GS_START_LAG = 0x10,
            W3GS_STOP_LAG = 0x11,
            W3GS_HOST_KICK_PLAYER = 0x1C,
            W3GS_REQJOIN = 0x1E,
            W3GS_LEAVEGAME = 0x21,
            W3GS_GAMELOADED_SELF = 0x23,
            W3GS_OUTGOING_ACTION = 0x26,
            W3GS_OUTGOING_KEEPALIVE = 0x27,
            W3GS_CHAT_TO_HOST = 0x28,
            W3GS_DROPREQ = 0x29,
            W3GS_SEARCHGAME = 0x2F, //(UDP/LAN)
            W3GS_GAMEINFO = 0x30, //(UDP/LAN)
            W3GS_CREATEGAME = 0x31, //(UDP/LAN)
            W3GS_REFRESHGAME = 0x32, //(UDP/LAN)
            W3GS_DECREATEGAME = 0x33, //(UDP/LAN)
            W3GS_CHAT_OTHERS = 0x34,
            W3GS_PING_FROM_OTHERS = 0x35,
            W3GS_PONG_TO_OTHERS = 0x36,
            W3GS_MAPCHECK = 0x3D,
            W3GS_STARTDOWNLOAD = 0x3F,
            W3GS_MAPSIZE = 0x42,
            W3GS_MAPPART = 0x43,
            W3GS_MAPPARTOK = 0x44,
            W3GS_MAPPARTNOTOK = 0x45, // just a guess, received this packet after forgetting to send a crc in W3GS_MAPPART (f7 45 0a 00 01 02 01 00 00 00)
            W3GS_PONG_TO_HOST = 0x46,
            W3GS_INCOMING_ACTION2 = 0x48 // received this packet when there are too many actions to fit in W3GS_INCOMING_ACTION
        };

        public W3GSPacket(Packet GamePacket)
            : base(GamePacket)
        {
        }

        public W3GSPacketIDConstants PacketType
        {
            get
            {
                return (W3GSPacketIDConstants)this.PacketCommand;
            }
        }

        public Int32 Port
        {
            get
            {
                if (portIdx == -1)
                    throw new Exception("Port Offset not specified");

                int port = 0;
                byte[] portRange = Payload.Range(portIdx, portIdx + 2);
                if (BitConverter.IsLittleEndian)
                    port = BitConverter.ToInt16(portRange.Reverse().ToArray(), 0);
                else
                    port = BitConverter.ToInt16(portRange, 0);

                return port;
            }
        }

        public String IP
        {
            get
            {
                if (ipIdx == -1)
                    throw new Exception("IP Address Offset not specified");

                int startIdx = ipIdx;
                byte[] ipAddress = Payload.Range(startIdx, startIdx + 4);

                return String.Format("{0}.{1}.{2}.{3}",
                    ipAddress[0],
                    ipAddress[1],
                    ipAddress[2],
                    ipAddress[3]
                    );

                return String.Empty;
            }
        }
    }

    public class W3GSChatOthersPacket : W3GSPacket
    {
        public W3GSChatOthersPacket(Packet GamePacket)
            : base(GamePacket)
        {
        }

        public String GetMessage()
        {
            return Payload.ExtractString(12);
        }
    }

    public class W3GSSlotInfoJoinPacket : W3GSPacket
    {
        public W3GSSlotInfoJoinPacket(Packet GamePacket)
            : base(GamePacket)
        {
        }
    }

    public class W3GSPlayerLeavePacket : W3GSPacket
    {
        public W3GSPlayerLeavePacket(Packet GamePacket)
            : base(GamePacket)
        {
        }

        public int GetPID()
        {
            byte[] byteArray = { 0, Payload[5] };
            int pid = 0;
            if (BitConverter.IsLittleEndian)
                byteArray = byteArray.Reverse().ToArray();

            pid = BitConverter.ToInt16(byteArray, 0);

            return pid;
        }
    }

    public class W3GSPlayerInfoPacket : W3GSPacket
    {
        public W3GSPlayerInfoPacket(Packet GamePacket)
            : base(GamePacket)
        {
            this.portIdx = GetName().Length + 14;
            this.ipIdx = GetName().Length + 16;
        }

        public String GetName()
        {
            return Payload.ExtractString(9);
        }

        public int GetPID()
        {
            byte[] byteArray = { 0, Payload[8] };
            int pid = 0;
            if (BitConverter.IsLittleEndian)
                byteArray = byteArray.Reverse().ToArray();

            pid = BitConverter.ToInt16(byteArray, 0);

            return pid;
        }
    }

    public class W3GSReqJoinPacket : W3GSPacket
    {
        public W3GSReqJoinPacket(Packet GamePacket)
            : base(GamePacket)
        {
            this.portIdx = GetName().Length + 24;
            this.ipIdx = GetName().Length + 26;
        }

        public String GetName()
        {
            return Payload.ExtractString(19);
        }
    }

    public class W3GSSlotInfoPacket : W3GSPacket
    {
        public enum SlotStatus : byte { Unknown = 255, Open = 0, Closed, Occupied };
        public enum SlotRace : byte { Unknown = 255, Human = 1, Orc = 2, NightElf = 4, Undead = 8, Random = 32, Fixed = 64 }
        public enum SlotComp : byte { Unknown = 255, Easy = 0, Normal = 1, Hard = 2 };

        public class GameSlot
        {
            public byte id = 255;
            public byte downloadStatus = 255;
            private byte slotStatus = 255;
            public byte computer = 255;
            public byte team = 255;
            public byte color = 255;
            private byte race = 255;
            private byte computerType = 255;

            public SlotComp ComputerType
            {
                get { return (SlotComp)computerType; }
            }
            public byte handicap = 255;

            public SlotRace Race
            {
                get { return (SlotRace)race; }

            }

            public SlotStatus SlotStatus
            {
                get
                {
                    return (SlotStatus)slotStatus;
                }
            }

            public GameSlot()
            {
            }

            public GameSlot(byte[] slotData)
            {
                for (int i = 0; i < slotData.Length; i++)
                {
                    switch (i)
                    {
                        case 0:
                            id = slotData[i];
                            break;
                        case 1:
                            downloadStatus = slotData[i];
                            break;
                        case 2:
                            slotStatus = slotData[i];
                            break;
                        case 3:
                            computer = slotData[i];
                            break;
                        case 4:
                            team = slotData[i];
                            break;
                        case 5:
                            color = slotData[i];
                            break;
                        case 6:
                            race = slotData[i];
                            break;
                        case 7:
                            computerType = slotData[i];
                            break;
                        case 8:
                            handicap = slotData[i];
                            break;
                        default:
                            throw new Exception("Slot Info Wrong Size!");
                            break;
                    }
                }
            }
        };

        public W3GSSlotInfoPacket(Packet GamePacket)
            : base(GamePacket)
        {
        }

        public int GetSlotInfoSize()
        {
            byte[] byteArray = Payload.Range(5, 7);
            int slotSize = 0;
            if (BitConverter.IsLittleEndian)
                byteArray = byteArray.Reverse().ToArray();

            slotSize = BitConverter.ToInt16(byteArray, 0);

            return slotSize;
        }

        public int GetPID()
        {
            int pidIdx = GetSlotInfoSize() * 9 + 6;
            return Payload.Range(pidIdx, pidIdx + 1)[0].ToInt32();
        }


        const int slotDataStartPos = 7;

        public List<GameSlot> DecodeSlots()
        {
            List<GameSlot> gameSlotList = new List<GameSlot>();

            for (int slot = 0; slot < GetSlotInfoSize(); slot++)
            {
                int startPos = (slot * 9) + slotDataStartPos;
                int endPos = startPos + 9;

                byte[] slotDataArray = Payload.Range(startPos, endPos);

                GameSlot g = new GameSlot(slotDataArray);
                gameSlotList.Add(g);
            }

            return gameSlotList;
        }
    }

    public class W3GSActionPacket : W3GSPacket
    {
        byte[] ActionHeader =
        {
                0x6b, 0x64, 0x72, 0x2e, 0x78, 0x00
            };

        List<Action> actions = new List<Action>();

        public List<Action> Actions
        {
            get { return actions; }
        }

        public W3GSActionPacket(Packet GamePacket)
            : base(GamePacket)
        {
            ProcessActionData();
        }

        public int CRC
        {
            get { return BitConverter.ToInt32(GamePacket.Data, 4); }
        }

        public byte[] RawActionData
        {
            get { return GamePacket.Data.Range(8); }
        }

        public void ProcessActionData()
        {
            int i = 0;

            while (RawActionData.Length >= i + 6)
            {
                byte[] actionDataHeader = RawActionData.Range(i, i + 6);

                bool headersEqual = true;

                for (int x = 0; x < 6; x++)
                {
                    if (actionDataHeader[x] != ActionHeader[x])
                    {
                        headersEqual = false;
                        break;
                    }
                }

                if (headersEqual)
                {
                    String data = RawActionData.ExtractString(i + 6);
                    String key = RawActionData.ExtractString(i + 7 + data.Length);

                    //  int value2 = BitConverter.ToInt32(RawActionData.Range(i + 12 + data.Length + key.Length, i + 12 + data.Length + key.Length + 4), 0);
                    int value = BitConverter.ToInt32(RawActionData, i + 8 + data.Length + key.Length);

                    Regex regex = new Regex(@"\d_\d");
                    if (regex.IsMatch(key))
                    {
                        int start = i + 8 + data.Length + key.Length;
                        String itemVal = RawActionData.Range(start, start + 4).GetString();
#if DEBUG
                        Console.WriteLine("Data: {1}, Key: {0}, Value: {2}", key, data, itemVal);
#endif
                        actions.Add(new Action(data, key, itemVal));
                    }
                    else
                    {
#if DEBUG
                        Console.WriteLine("Data: {1}, Key: {0}, Value: {2}", key, data, value);
#endif
                        actions.Add(new Action(data, key, value));
                    }

                    i += 8 + data.Length + key.Length + 4;
                }
                else
                    i++;
            }

        }
    }

    public class Action
    {
        public enum ActionTypes { Unknown, HeroDeath };

        String Data = String.Empty;
        String Key = String.Empty;
        String ValueString = String.Empty;
        Int32 ValueInt = 0;

        public String KillerColor = String.Empty;
        public String VictimColor = String.Empty;

        public ActionTypes ActionType = ActionTypes.Unknown;

        public Action(String Data, String Key, String Value)
        {
            this.Data = Data;
            this.Key = Key;
            this.ValueString = Value;

            Process();
        }

        public Action(String Data, String Key, Int32 Value)
        {
            this.Data = Data;
            this.Key = Key;
            this.ValueInt = Value;

            Process();
        }

        private void Process()
        {
            if (Key.StartsWith("Hero"))
            {
                ActionType = ActionTypes.HeroDeath;
                VictimColor = Key.Substring(4);
                KillerColor = ValueInt.ToString();
            }
        }
    }
}
